#include "engine.h"
#include "QDebug"
#include <queue>
#include <Windows.h>
using namespace std;

double distribute_more[5] = {3.0/17.0, 3.0/17.0, 3.0/17.0, 4.0/17.0, 4.0/17.0};
double distribute_less[3] = {1.0/3.0,1.0/3.0,1.0/3.0};
double distribute_least[2] = {1};
const int thread_least_chess_num = 45;
const int no_thread_change = 55;
int max_depth,sec_width;
struct Step_with_score{
    Step s;
    int score;
    Step_with_score(){}
    Step_with_score(Step s,int score){
        this->s = s;
        this->score = score;
    }
    bool operator<(const Step_with_score &stepws) const{
        if(score < stepws.score) return true;
        else return false;
    }
    bool operator>(const Step_with_score &stepws) const{
        if(score > stepws.score) return true;
        else return false;
    }
    bool operator==(const Step_with_score &stepws) const{
        if(score == stepws.score) return true;
        else return false;
    }
};

void Engine::best_step(int player){
    has_return = false;
    HashManger::i = 0,HashManger::shot = 0;
    vector <Step> next;
    int topA = -UNTOUCH;
    int a = topA,b = UNTOUCH;
    Cb_with_num cb0(*this->cb);
    find_sum_step(player,next,cb0);
    vector<Step_with_score> step_score;

    krt = get_krt(cb->chess_num),qrt = get_qrt(cb->chess_num),drt = get_drt(cb->chess_num);
    int size = next.size();
    for(auto i:next) step_score.push_back(Step_with_score(i,ef(i,player,cb0)));
    sort(step_score.begin(),step_score.end(),[](const Step_with_score & s1,const Step_with_score &s2){
        return s1.score> s2.score;
    });
    best = step_score[0].s;
    vector<future<void>> fu;
    mutex lock;
    int WIDTH = get_FD(cb0.chess_num);
    max_depth = get_depth(cb0.chess_num),sec_width = get_width(cb0.chess_num);
    qDebug() << '\n' << '\n' <<"chess_num :"<< cb->chess_num ;
    qDebug() << "depth : " << max_depth << "width: "<< sec_width;
    qDebug() << "king ratio : " << (int)krt << "queen ratio : "<< (int)qrt << "dim ratio : " << (int)drt;
    int m = 0;
    double * distribute;
    if(cb0.chess_num < thread_least_chess_num) distribute = distribute_more;
    else if(cb0.chess_num < no_thread_change ) distribute = distribute_less;
    else distribute = distribute_least;
    thread_count++;
    double finishcount = -1;
    if(if_use_hash) {
        while(!hash_manger->is_finished()) Sleep(100);
        if(!if_load_after_cal) hash_manger->load_current(cb->chess_num,get_depth(cb->chess_num));
    }
    double addnext = distribute[m]*WIDTH;
    for (double i = 0;i < WIDTH &&i < size;i += addnext) {
        qDebug() << "thread"<< thread_count <<" start: " <<i;
        if(i!=0) m++;
        addnext = distribute[m]*WIDTH;
        fu.push_back(async(launch::async,[&a,&b,this,player,&step_score,&topA,&lock,i,WIDTH,size,addnext,&cb0,&finishcount](){
            Cb_with_num cb(cb0);
            for (int j = i;j < i + addnext && j < WIDTH && j < size;j++ ) {
                if(has_return) {
                    qDebug() << "thread"<< thread_count <<" finished: " << i;
                    return;
                }
                Step s = step_score[j].s;
                temp_set(s,player,cb);
                int ss = dfs(s,!player,a,b,cb);
                temp_back(s,player,cb);

                {
                    lock_guard<mutex> lg(lock);
                    finishcount++;
                    if(ss < b) b = ss;
                    if(b > topA) {
                        topA = b;
                        best = step_score[j].s;
                    }
                    a = topA;
                    b = UNTOUCH;
                    //qDebug() << a << cb.chess_num;
                }
            }
            qDebug() << "thread"<< thread_count <<" finished: " <<i;
        }));
    }
    bool is_killed =false;
    if(limit_time){
        t = new class thread ([this,&is_killed](){
            int i = 0;
            while(i < max_consider_time*10){
                if(!has_return) Sleep(100);
                else break;
                i++;
            }
            if(!has_return){
                if(if_use_hash) if(!hash_manger->is_finished()) return;
                has_return = true;
                is_killed = true;
            }
        });
        t->detach();
    }
    for(auto &t:fu) t.wait();
    has_return = true;
    emit notyify_ui(best);
    if(if_use_hash){
        double eval  =  HashManger::shot / HashManger::i;
        qDebug() << "eavl:" << eval;
        if(eval > 0.4) now_search_mode = 1;
        else now_search_mode = 2;
        if(if_load_after_cal) hash_manger->load_future(cb->chess_num,get_depth(cb->chess_num + 2));
    }
    qDebug() << "finished ratio :" << finishcount / (double)min(size,WIDTH);
    qDebug() << "nodes: " << (double)min(size,WIDTH) << "finished nodes" << finishcount;
    if(is_killed) qDebug() << "killed";
    else qDebug() << "normal finished";
}


bool isMin(int depth){
    return depth%2 == 0;
}

int Engine::dfs(const Step &s, int player,int a,int b,Cb_with_num &cb,int depth){
    if(has_return) return 0;
    int win = winner(cb);
    if(win != 2){
        if(win == *this->player || win == 3) return UNTOUCH;
        else return -UNTOUCH;
    }

    if(depth >= max_depth){
        return ef(s,player,cb);
    }

    vector<Step> next;
    if(isMin(depth)){
        find_sum_step(player,next,cb);
        int size = next.size();
        if(depth < max_depth-1){
            vector<Step_with_score> step_score;
            for(auto &i:next) step_score.push_back(Step_with_score(i,ef(i,player,cb)));
            sort(step_score.begin(),step_score.end());
            size = min(sec_width,size);
            for (int i = 0;i < size;i++ ) {
                if(a >= b) break;
                const Step &s = step_score[i].s;
                temp_set(s,player,cb);
                int score = dfs(s,!player,a,b,cb,depth+1);
                temp_back(s,player,cb);

                if(score < b) b = score;
            }
        }
        else
            for (int i = 0;i < size;i++ ) {
                if(a >= b) break;
                const Step &s = next[i];
                temp_set(s,player,cb);
                int score = dfs(s,!player,a,b,cb,depth+1);
                temp_back(s,player,cb);

                if(score < b) b = score;
            }

        return b;
    }
    else{
        find_sum_step(player,next,cb);
        int size = next.size();
        if(depth < max_depth -1){
            vector<Step_with_score> step_score;
            for(auto i:next) step_score.push_back(Step_with_score(i,ef(i,player,cb)));
            sort(step_score.begin(),step_score.end(),[](const Step_with_score & s1,const Step_with_score &s2){
                return s1.score> s2.score;
            });
            size = min(sec_width,size);
            for (int i = 0;i < size ;i++ ) {
                if(a >= b) break;
                const Step &s = step_score[i].s;
                temp_set(s,player,cb);
                int score = dfs(s,!player,a,b,cb,depth+1);
                temp_back(s,player,cb);

                if(score > a) a = score;
            }
        }
        else
            for (int i = 0;i < size ;i++ ) {
                if(a >= b) break;
                const Step &s = next[i];
                temp_set(s,player,cb);
                int score = dfs(s,!player,a,b,cb,depth+1);
                temp_back(s,player,cb);

                if(score > a) a = score;
            }
        return a;
    }

}

void Engine::temp_set(const Step &s, int player,Cb_with_num &cb){
    if(player == blackc){
        for (int i = 0;i < 4;i++) {
            if( cb.movechess.blackchess[i] == s.oldchess) {
                cb.movechess.blackchess[i] = s.chess;
                break;
            }
        }
        cb.chessborad[s.oldchess] = 2;
        cb.chessborad[s.chess] = player;
        cb.chessborad[s.hinder] = blackh;
    }
    else {
        for (int i = 0;i < 4;i++) {
            if( cb.movechess.whitechess[i] == s.oldchess) {
                cb.movechess.whitechess[i] = s.chess;
                break;
            }
        }
        cb.chessborad[s.oldchess] = 2;
        cb.chessborad[s.chess] = player;
        cb.chessborad[s.hinder] = whiteh;
    }
    cb.chess_num ++;
}

void Engine::temp_back(const Step &s,int player,Cb_with_num &cb){
    if(player == blackc){
        for (int i = 0;i < 4;i++) {
            if( cb.movechess.blackchess[i] == s.chess) {
                cb.movechess.blackchess[i] = s.oldchess;
                break;
            }
        }
    }
    else {
        for (int i = 0;i < 4;i++) {
            if( cb.movechess.whitechess[i] == s.chess) {
                cb.movechess.whitechess[i] = s.oldchess;
                break;
            }
        }
    }
    cb.chessborad[s.hinder] = 2;
    cb.chessborad[s.chess] = 2;
    cb.chessborad[s.oldchess] = player;
    cb.chess_num--;
}

int e1max[100] = {0},e2max[100] = {0};
int i = 0;

int Engine::get_depth(int chess_num){
    if(!computer_remote){
        if(chess_num < S1) return DEPTH0;
        else if(chess_num < S2) return DEPTH1;
        else if(chess_num < S3) return DEPTH2;
        else if(chess_num < S4) return DEPTH3;
        else if(chess_num < S5) return DEPTH4;
        else if(chess_num < S6) return DEPTH5;
        else if(chess_num < S7) return DEPTH6;
        else return DEPTH7;
    }
    else{
        if(chess_num < S1_2) return DEPTH0_2;
        else if(chess_num < S2_2) return DEPTH1_2;
        else if(chess_num < S3_2) return DEPTH2_2;
        else if(chess_num < S4_2) return DEPTH3_2;
        else if(chess_num < S5_2) return DEPTH4_2;
        else if(chess_num < S6_2) return DEPTH5_2;
        else if(chess_num < S7_2) return DEPTH6_2;
        else return DEPTH7_2;
    }
}

int Engine::get_width(int chess_num){
    if(!computer_remote){
        if(chess_num < S1) return WIDTH0;
        else if(chess_num < S2) return WIDTH1;
        else if(chess_num < S3) return WIDTH2;
        else if(chess_num < S4) return WIDTH3;
        else if(chess_num < S5) return WIDTH4;
        else if(chess_num < S6) return WIDTH5;
        else if(chess_num < S7) return WIDTH6;
        else return WIDTH7;
    }
    else {
        if(chess_num < S1_2) return WIDTH0_2;
        else if(chess_num < S2_2) return WIDTH1_2;
        else if(chess_num < S3_2) return WIDTH2_2;
        else if(chess_num < S4_2) return WIDTH3_2;
        else if(chess_num < S5_2) return WIDTH4_2;
        else if(chess_num < S6_2) return WIDTH5_2;
        else if(chess_num < S7_2) return WIDTH6_2;
        else return WIDTH7_2;
    }
}

int Engine::get_FD(int chess_num){
    if(!computer_remote){
        if(chess_num < S1) return FW0;
        else if(chess_num < S2) return FW1;
        else if(chess_num < S3) return FW2;
        else if(chess_num < S4) return FW3;
        else if(chess_num < S5) return FW4;
        else if(chess_num < S6) return FW5;
        else if(chess_num < S7) return FW6;
        else return FW7;
    }
    else {
        if(chess_num < S1_2) return FW0_2;
        else if(chess_num < S2_2) return FW1_2;
        else if(chess_num < S3_2) return FW2_2;
        else if(chess_num < S4_2) return FW3_2;
        else if(chess_num < S5_2) return FW4_2;
        else if(chess_num < S6_2) return FW5_2;
        else if(chess_num < S7_2) return FW6_2;
        else return FW7_2;
    }
}
